﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using PloobsEngine.Light;
using PloobsEngine.Cameras;

namespace PloobsEngine.SceneControl
{
    public class LightMap : IDeferredLightMap
    {
        private QuadRender quadRenderer;
        private EngineStuff engine;
        private RenderTarget2D lightRT;
        private Effect directionalLightEffect;
        private Effect pointLightEffect;
        private Effect spotLightEffect;
        private Model sphereModel;
        private Model coneModel;
        private bool cullPointLight = true;        
        
        #region IDeferredLightMap Members        
        private void DrawDirectionalLight(ICamera camera, IList<ILight> lights, IDeferredGBuffer DeferredGBuffer)
        {
            directionalLightEffect.Parameters["colorMap"].SetValue(DeferredGBuffer[GBufferTypes.COLOR]);
            directionalLightEffect.Parameters["normalMap"].SetValue(DeferredGBuffer[GBufferTypes.NORMAL]);
            directionalLightEffect.Parameters["depthMap"].SetValue(DeferredGBuffer[GBufferTypes.DEPH]);
            directionalLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
            directionalLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.View * camera.Projection));
            directionalLightEffect.Parameters["halfPixel"].SetValue(EngineStuff.HalfPixel);
            
            foreach (ILight item in lights)
            {
                if (item.LightType == LightType.Deferred_Directional)
                {
                    DirectionalLight dl = item as DirectionalLight;

                    directionalLightEffect.Parameters["lightDirection"].SetValue(dl.LightDirection);
                    directionalLightEffect.Parameters["Color"].SetValue(dl.Color.ToVector3());
                    directionalLightEffect.Parameters["lightIntensity"].SetValue(dl.LightIntensity);

                    directionalLightEffect.Begin();
                    directionalLightEffect.Techniques[0].Passes[0].Begin();
                    quadRenderer.Render(Vector2.One * -1, Vector2.One);
                    directionalLightEffect.Techniques[0].Passes[0].End();
                    directionalLightEffect.End();

                }
            }
        }

        private void DrawPointLight(ICamera camera, IList<ILight> lights, IDeferredGBuffer DeferredGBuffer)
        {

            pointLightEffect.Parameters["colorMap"].SetValue(DeferredGBuffer[GBufferTypes.COLOR]);
            pointLightEffect.Parameters["normalMap"].SetValue(DeferredGBuffer[GBufferTypes.NORMAL]);
            pointLightEffect.Parameters["depthMap"].SetValue(DeferredGBuffer[GBufferTypes.DEPH]);
            pointLightEffect.Parameters["Projection"].SetValue(camera.Projection);
            pointLightEffect.Parameters["View"].SetValue(camera.View);
            pointLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);
            pointLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.View * camera.Projection));
            pointLightEffect.Parameters["halfPixel"].SetValue(EngineStuff.HalfPixel);

            foreach (ILight item in lights)
            {
                if (item.LightType == LightType.Deferred_Point)
                {

                    PointLight pl = item as PointLight;
                    Matrix sphereWorldMatrix = Matrix.CreateScale(pl.LightRadius) * Matrix.CreateTranslation(pl.LightPosition);
                    
                    if (cullPointLight == true || camera.BoundingFrustum.Contains(sphereModel.Meshes[0].BoundingSphere.Transform(sphereWorldMatrix)) != ContainmentType.Disjoint)
                    {
                        pointLightEffect.Parameters["World"].SetValue(sphereWorldMatrix);
                        pointLightEffect.Parameters["lightPosition"].SetValue(pl.LightPosition);
                        pointLightEffect.Parameters["Color"].SetValue(pl.Color.ToVector3());
                        pointLightEffect.Parameters["lightRadius"].SetValue(pl.LightRadius);
                        pointLightEffect.Parameters["lightIntensity"].SetValue(pl.LightIntensity);
                        pointLightEffect.Parameters["quadratic"].SetValue(pl.UsePointLightQuadraticAttenuation);


                        float cameraToCenter = Vector3.Distance(camera.Position, pl.LightPosition);

                        if (cameraToCenter < pl.LightRadius )
                            EngineStuff.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
                        else 
                            EngineStuff.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;

                        pointLightEffect.Begin();
                        pointLightEffect.Techniques[0].Passes[0].Begin();
                        
                        foreach (ModelMesh mesh in sphereModel.Meshes)
                        {
                            foreach (ModelMeshPart meshPart in mesh.MeshParts)
                            {
                                EngineStuff.GraphicsDevice.VertexDeclaration = meshPart.VertexDeclaration;
                                EngineStuff.GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
                                EngineStuff.GraphicsDevice.Indices = mesh.IndexBuffer;
                                EngineStuff.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, meshPart.BaseVertex, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
                            }
                        }

                        pointLightEffect.Techniques[0].Passes[0].End();
                        pointLightEffect.End();

                    }
                }
            }

            EngineStuff.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            
        }

        private void DrawnSpotLight(ICamera camera, IList<ILight> lights, IDeferredGBuffer DeferredGBuffer)
        {
            spotLightEffect.Parameters["colorMap"].SetValue(DeferredGBuffer[GBufferTypes.COLOR]);
            spotLightEffect.Parameters["normalMap"].SetValue(DeferredGBuffer[GBufferTypes.NORMAL]);
            spotLightEffect.Parameters["depthMap"].SetValue(DeferredGBuffer[GBufferTypes.DEPH]);

            spotLightEffect.Parameters["View"].SetValue(camera.View);
            spotLightEffect.Parameters["Projection"].SetValue(camera.Projection);
            spotLightEffect.Parameters["cameraPosition"].SetValue(camera.Position);


            spotLightEffect.Parameters["InvertViewProjection"].SetValue(Matrix.Invert(camera.View * camera.Projection));
            spotLightEffect.Parameters["halfPixel"].SetValue(EngineStuff.HalfPixel);

            
            foreach (ILight item in lights)
            {
                if (item.LightType == LightType.Deferred_Spot)
                {
                    SpotLight sl = item as SpotLight;
                    spotLightEffect.Parameters["lightPosition"].SetValue(sl.Position);
                    spotLightEffect.Parameters["lightDirection"].SetValue(sl.Direction);
                    spotLightEffect.Parameters["lightRadius"].SetValue(sl.LightRadius);
                    spotLightEffect.Parameters["lightDecayExponent"].SetValue(sl.ConeDecay);
                    spotLightEffect.Parameters["Color"].SetValue(sl.Color.ToVector3());
                    spotLightEffect.Parameters["lightAngleCosine"].SetValue(sl.LightAngleCosine);
                    spotLightEffect.Parameters["lightIntensity"].SetValue(sl.LightIntensity);

                    //double angle = Math.Pow(sl.LightAngleCosine, 2);
                    //double lateralScale = sl.LightRadius * (Math.Sqrt((1 / angle) - 1));
                    //Matrix sphereWorldMatrix = Matrix.CreateScale((float)lateralScale, sl.LightRadius, (float)lateralScale) * Matrix.CreateWorld(sl.Position, sl.Direction, Vector3.Up);
                    //spotLightEffect.Parameters["World"].SetValue(sphereWorldMatrix);


                    CullMode cm = EngineStuff.GraphicsDevice.RenderState.CullMode; 
                    EngineStuff.GraphicsDevice.RenderState.CullMode = CullMode.None;
                    
                    spotLightEffect.Begin();                    
                    spotLightEffect.Techniques[0].Passes[0].Begin();

                    EngineStuff.GraphicsDevice.VertexDeclaration = fsVertexDeclaration;
                    EngineStuff.GraphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleStrip, fsVertices, 0, 2);
                    //foreach (ModelMesh mesh in coneModel.Meshes)
                    //{
                    //    foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    //    {
                    //        EngineStuff.GraphicsDevice.VertexDeclaration = meshPart.VertexDeclaration;
                    //        EngineStuff.GraphicsDevice.Vertices[0].SetSource(mesh.VertexBuffer, meshPart.StreamOffset, meshPart.VertexStride);
                    //        EngineStuff.GraphicsDevice.Indices = mesh.IndexBuffer;
                    //        EngineStuff.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, meshPart.BaseVertex, 0, meshPart.NumVertices, meshPart.StartIndex, meshPart.PrimitiveCount);
                    //    }
                    //}
                    spotLightEffect.Techniques[0].Passes[0].End();
                    spotLightEffect.End();
                    EngineStuff.GraphicsDevice.RenderState.CullMode = cm; 
                }
            }
        }

        VertexPositionTexture[] fsVertices;
        VertexDeclaration fsVertexDeclaration;
        private void InitFullscreenVertices()
        {
            fsVertices = new VertexPositionTexture[4];
            int i = 0;
            fsVertices[i++] = new VertexPositionTexture(new Vector3(-1, 1, 0f), new Vector2(0, 0));
            fsVertices[i++] = new VertexPositionTexture(new Vector3(1, 1, 0f), new Vector2(1, 0));
            fsVertices[i++] = new VertexPositionTexture(new Vector3(-1, -1, 0f), new Vector2(0, 1));
            fsVertices[i++] = new VertexPositionTexture(new Vector3(1, -1, 0f), new Vector2(1, 1));
            fsVertexDeclaration = new VertexDeclaration(EngineStuff.GraphicsDevice, VertexPositionTexture.VertexElements);
        }        



        public void DrawLights(GameTime gameTime,IWorld world, IDeferredGBuffer deferredGBuffer)
        {
            EngineStuff.GraphicsDevice.Clear(Color.TransparentBlack);
            EngineStuff.GraphicsDevice.RenderState.AlphaBlendEnable = true;
            EngineStuff.GraphicsDevice.RenderState.AlphaBlendOperation = BlendFunction.Add;
            EngineStuff.GraphicsDevice.RenderState.SourceBlend = Blend.One;
            EngineStuff.GraphicsDevice.RenderState.DestinationBlend = Blend.One;
            EngineStuff.GraphicsDevice.RenderState.SeparateAlphaBlendEnabled = false;
            EngineStuff.GraphicsDevice.RenderState.DepthBufferEnable = false;

            DrawDirectionalLight(world.ActiveCamera, world.Lights,deferredGBuffer);
            DrawPointLight(world.ActiveCamera, world.Lights,deferredGBuffer);
            DrawnSpotLight(world.ActiveCamera, world.Lights, deferredGBuffer);

            EngineStuff.GraphicsDevice.RenderState.AlphaBlendEnable = false;
        }

        public void LoadContent(EngineStuff engine, bool useFloatBuffer)
        {
            this.engine = engine;

            quadRenderer = new QuadRender(EngineStuff.GraphicsDevice);            
            if(useFloatBuffer)            
                lightRT = EngineStuff.GetDefaultBuffer(SurfaceFormat.HalfVector4);
            else
                lightRT = EngineStuff.GetDefaultColorBuffer();

            directionalLightEffect = EngineStuff.InternalContentManager.GetAsset<Effect>("DirectionalLight");
            pointLightEffect = EngineStuff.InternalContentManager.GetAsset<Effect>("PointLight");
            //pointLightEffect = EngineStuff.CustomContentManager.GetAsset<Effect>("..\\Content\\Effects\\PointLight");
            sphereModel = EngineStuff.InternalContentManager.GetAsset<Model>("Dsphere");
            coneModel = EngineStuff.InternalContentManager.GetAsset<Model>("cone");
            spotLightEffect = EngineStuff.InternalContentManager.GetAsset<Effect>("SpotLight");
            InitFullscreenVertices();

        }

        #endregion

        #region IDeferredLightMap Members


        public Texture2D this[DeferredLightMapType type]
        {
            get {
                switch (type)
                {
                    case DeferredLightMapType.LIGHTMAP:
                        return lightRT.GetTexture();
                    default:
                        throw new Exception("Type not present in this implementation");
                }
                
            
            }
            set
            {
                throw new Exception("Cant do this in this implementation");
            }
        }

        #endregion

        #region IDeferredLightMap Members

        public void SetLightMap()
        {
            EngineStuff.GraphicsDevice.SetRenderTarget(0, lightRT);
        }

        public void ResolveLightMap()
        {
            EngineStuff.GraphicsDevice.SetRenderTarget(0, null);
        }


        public bool CullPointLight
        {
            get { return cullPointLight; }
            set { cullPointLight = value; }
        }

        #endregion
    }
}

